<?php

/**
 * Main application launcher
 * @package Nina
 * @author John Main <john@thedaddy.org>, Andrew Rose
 */
class Nina_Launcher
{
	protected $reg;
	protected $logger;
	protected $response;

	/**
	 * Constructor
	 * Sets up the registry and parses the ini file.
	 */
	public function __construct() {
		define("NINA_INI_SANE", TRUE);

		require_once NINA_ROOT.DIRECTORY_SEPARATOR.'Nina.php';
		require_once NINA_ROOT.DIRECTORY_SEPARATOR.'Nina'.DIRECTORY_SEPARATOR.'Ini.php';
		require_once NINA_ROOT.DIRECTORY_SEPARATOR.'Nina'.DIRECTORY_SEPARATOR.'Registry.php';

		$this->reg = Nina_Registry::getInstance();
		$this->reg->import(Nina_Ini::parse(file_get_contents(NINA_INI)));

		if(defined('URL_ROOT') && isset($_SERVER['SERVER_NAME']) && $_SERVER['SERVER_NAME'] != URL_ROOT && $this->reg->get("core.force_url_root")) {
			header('HTTP/1.1 301 Moved Permanently');
			header('Location: http://'.URL_ROOT);
			exit();
  		}

		//Makes it easier to put login in the parent app with reference to PARENT_ROOT
		if(!defined('PARENT_ROOT'))
			define('PARENT_ROOT', APP_ROOT);

		if(!defined('INCLUDE_ROOT')) {
			if(is_dir(APP_ROOT . DIRECTORY_SEPARATOR . 'include'))
				define('INCLUDE_ROOT', APP_ROOT . DIRECTORY_SEPARATOR . 'include');
			elseif(is_dir(PARENT_ROOT . DIRECTORY_SEPARATOR . 'include'))
				define('INCLUDE_ROOT', PARENT_ROOT . DIRECTORY_SEPARATOR . 'include');
		}


		if(!defined('MODEL_ROOT')) {
			if(is_dir(APP_ROOT . DIRECTORY_SEPARATOR . 'models'))
				define('MODEL_ROOT', APP_ROOT . DIRECTORY_SEPARATOR . 'models');
			elseif(is_dir(PARENT_ROOT . DIRECTORY_SEPARATOR . 'models'))
				define('MODEL_ROOT', PARENT_ROOT . DIRECTORY_SEPARATOR . 'models');
			else
				throw new Nina_Exception('Cannot find model root');
		}

		if ($this->reg->get("login.type") == 'cookie') {
			if(!session_id())
				session_start();
		}
	}

	/**
	 * Load all the necessary stuff for the environment to run
	 * Seperate from launch so that the framework can be initialised without rendering any pages
	 */
	public function loadEnvironment() {

		/*
		 * Register the autoloader and exception handler
		 */
		set_exception_handler(array('Nina', 'exceptionHandler'));

		//Set the debug level
		if ($this->reg->get('logging.debug')) {
			error_reporting(E_ALL & ~E_STRICT);
			ini_set('display_errors', true);
		}
		else {
			error_reporting(0);

			if ($this->reg->get('logging.verbose'))
				ini_set('display_errors', true);
			else
				ini_set('display_errors', false);
		}

		$this->loadLibraries();

		$this->logger = Nina_Logger::getInstance();

		$this->registerDbInstances();

		$user = new Nina_User();
		$this->reg->set("runtime.user", $user);

		if(class_exists('Client')) {
			$client = new Nina_Client();
			$this->reg->set("runtime.client", $client);
		}
	}

	/**
	 * Registers all required libraries with the autoloader
	 */
	protected function loadLibraries() {
		require_once NINA_ROOT.DIRECTORY_SEPARATOR.'Nina'.DIRECTORY_SEPARATOR.'Autoloader.php';
		require_once NINA_ROOT.DIRECTORY_SEPARATOR.'Nina'.DIRECTORY_SEPARATOR.'Autoloader'.DIRECTORY_SEPARATOR.'Nina.php';

		$nina_autoloader = new Nina_Autoloader_Nina(NINA_ROOT);
		$nina_autoloader->register();

		if(defined('COMMONS')) {
			$nina_autoloader = new Nina_Autoloader_Nina(COMMONS);
			$nina_autoloader->register();
		}

 		$nina_autoloader = new Nina_Autoloader_Nina(MODEL_ROOT);
 		$nina_autoloader->register();

		//Autoload both controller folders. The application ones will take priority
		if(is_dir(APP_ROOT . DIRECTORY_SEPARATOR . 'controllers')) {
 			$nina_autoloader = new Nina_Autoloader_Nina(APP_ROOT . DIRECTORY_SEPARATOR . 'controllers');
 			$nina_autoloader->register();
 		}

		if(is_dir(PARENT_ROOT . DIRECTORY_SEPARATOR . 'controllers')) {
 			$nina_autoloader = new Nina_Autoloader_Nina(PARENT_ROOT . DIRECTORY_SEPARATOR . 'controllers');
 			$nina_autoloader->register();
 		}


 		$nina_autoloader = new Nina_Autoloader_Nina(APP_ROOT . DIRECTORY_SEPARATOR . 'controllers');
 		$nina_autoloader->register();
	}

	/**
	 * Initialize the database.
	 * @throws Nina_Exception If the database cannot be started.
	 */
	protected function registerDbInstances() {
		if($this->reg->exists('database')) {
			foreach(array_keys($this->reg->get('database')) as $database) {
				//We may also get the migration folders appearing in this list. These are not databases to be loaded.
				if($database != 'migration_folders') {
					$db = new Nina_Db();

					try {
						$db->init($this->reg->getReference('database.'.$database));
						$this->reg->set('database.'.$database.'.instance', $db);
					}
					catch (Nina_Exception $exception) {
						$this->logger->critical($exception->getMessage());
						exit();
					}
				}
			}
		}
	}

	/**
	 * Renders the web page
	 */
	protected function loadPage() {
		$request = $this->getRequest();

		//Prepare default values
		$default_controller = ($this->reg->exists('core.default.controller')) ? $this->reg->get('core.default.controller') : 'default';
		$default_action = ($this->reg->exists('core.default.action')) ? $this->reg->get('core.default.action') : 'index';
		$default_id = ($this->reg->exists('core.default.id')) ? $this->reg->get('core.default.id') : 0;

		$controller_name = ($request->hasParameter('controller')) ? trim($request->getSanitisedParameter('controller')) : $default_controller;
		$action = ($request->hasParameter('action')) ? trim($request->getSanitisedParameter('action')) : $default_action;
		$id = $request->hasParameter('id') ? trim($request->getSanitisedParameter('id')) : $default_id;

/*
		if(file_exists(APP_ROOT.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.ucfirst($controller_name).'_Controller.php')) {
			require_once APP_ROOT.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.ucfirst($controller_name).'_Controller.php';
			$controller_class = ucfirst($controller_name).'_Controller';
			$controller = new $controller_class($action, $id);
		}
		elseif(file_exists(PARENT_ROOT.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.ucfirst($controller_name).'_Controller.php')) {
			require_once PARENT_ROOT.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.ucfirst($controller_name).'_Controller.php';
			$controller_class = ucfirst($controller_name).'_Controller';
			$controller = new $controller_class($action, $id);
		}
		elseif(defined('COMMONS')) {
			require_once COMMONS.DIRECTORY_SEPARATOR.'Wiki'.DIRECTORY_SEPARATOR.'Controller.php';
			$controller_class = 'Wiki_Controller';
			$controller = new $controller_class($controller_name, $action, $id);
		}
		else {
			$controller_name = $default_controller;

			$controller_class = ucfirst($controller_name).'_Controller';
			$controller = new $controller_class($action, $id);
		}

		*/
		if(file_exists(APP_ROOT.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.ucfirst($controller_name).'_Controller.php')
			|| (file_exists(PARENT_ROOT.DIRECTORY_SEPARATOR.'controllers'.DIRECTORY_SEPARATOR.ucfirst($controller_name).'_Controller.php'))) {
			$controller_class = ucfirst($controller_name).'_Controller';
			$controller = new $controller_class($action, $id);
		}
		elseif(defined('COMMONS')) {
			$controller_class = 'Wiki_Controller';
			$controller = new $controller_class($controller_name, $action, $id);
		}
		else {
			$controller_class = ucfirst($default_controller).'_Controller';
			$controller = new $controller_class($action, $id);

			if(method_exists($controller_class, $controller_name))
				$action = $controller_name;

			$controller_name = $default_controller;
		}

		if(method_exists($action, $controller_name))
			$action = $default_action;

		//Load the controller and call the action
		$controller->callAction($action);

		//if the original controller forwarded to another we need to pick up that instance
		$controller = $this->getController();
		$request = $this->getRequest();

		$controller_name = $controller->getController();
		$layout_name = $controller->getLayout();

		if($controller->getRenderlayout()) {
			if(file_exists(APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.$layout_name.'.php'))
				require_once APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.$layout_name.'.php';
			elseif(file_exists(APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.$controller_name.'.php'))
				require_once APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.$controller_name.'.php';
			elseif(defined('PARENT_ROOT')) {
				if(file_exists(APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.$controller_name.'.php'))
					require_once APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.$controller_name.'.php';
				elseif(file_exists(PARENT_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.$controller_name.'.php'))
					require_once PARENT_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.$controller_name.'.php';
				elseif(file_exists(APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.'default.php'))
					require_once APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.'default.php';
				else
					require_once PARENT_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.'default.php';
			}
			else
				require_once APP_ROOT.DIRECTORY_SEPARATOR.'layouts'.DIRECTORY_SEPARATOR.'default.php';
		}
		else
			$controller->render();
	}

	/**
	 * Get the current page request.
	 * @return Nina_Request
	 */
	public function getRequest() {
		if (!$this->reg->exists("runtime.request"))
			$this->reg->set("runtime.request", new Nina_Request());
		return $this->reg->get("runtime.request");
	}

	/**
	 * Get the current controller.
	 * @return Nina_Controller
	 */
	public function getController() {
		return $this->reg->get("runtime.controller");
	}

	/**
	 * Launches the application
	 */
	public function launch() {
		$this->loadEnvironment();

		//Check if we're running in command line mode
		if (php_sapi_name() == 'cli' || (isset($_SERVER['argc']) && $_SERVER['argc'] > 1)) {
			echo "Launching Command Line\n";
			$this->launchcli();
		}
		else {
			//Check if we need to be on a secure connection and redirect if necessary
			if ($this->reg->get("security.https")
				&& (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on' || $_SERVER['SERVER_PORT'] != 443)
				) {

				session_write_close();
				header('Location: https://'.$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"]);
				exit();
			}

			$this->loadPage();
		}
	}

	/**
	 * Launches the command line app
	 */
	public function launchcli() {

		//Instantiate the request object for compatibility purposes
		$this->getRequest();

		if(!$this->runCoreAction()) {
			$controller = (isset($_SERVER['argv'][1])) ? $_SERVER['argv'][1] : 'default';
			$action = (isset($_SERVER['argv'][2])) ? $_SERVER['argv'][2] : 'index';
			$id = (isset($_SERVER['argv'][3])) ? $_SERVER['argv'][3] : 0;

			if(file_exists(APP_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php')) {
				require_once APP_ROOT.'/controllers/'.ucfirst($controller).'_Controller.php';

				$controller_class = ucfirst($controller).'_Controller';

				$controller_instance = new $controller_class($action, $id);

				if(method_exists($controller_instance, $action))
					$controller_instance->callAction($action);
				else
					echo "Unknown Action\n";
			}
			else
				echo "Unknown Controller\n";
		}
	}

	/**
	 * Runs any core actions that may have been requested
	 * @return boolean
	 */
	protected function runCoreAction() {
		if(isset($_SERVER['argv'][1])) {
			$function = $_SERVER['argv'][1];

			$migration = new Nina_Migration();

			switch($function) {
				case 'migrate':
					echo "Running Migrations\n";
					$migration->migrate();
					echo "Complete\n";
					return true;
				case 'migrateCore':
					echo "Running Core Migrations\n";
					$migration->migrateCore();
					echo "Complete\n";
					return true;
				case 'migrateSite':
					echo "Running Site Migrations\n";
					$migration->migrateSite();
					echo "Complete\n";
					return true;
				case 'generate_migration':
				case 'generateCoreMigration':
					echo "Generating Core Migration\n";
					$title = (isset($_SERVER['argv'][2])) ? $_SERVER['argv'][2] : 'untitled_migration';
					$migration->generateCoreMigration($title);
					echo "Complete\n";
					return true;
				case 'generateSiteMigration':
					echo "Generating Site Migration\n";
					$title = (isset($_SERVER['argv'][2])) ? $_SERVER['argv'][2] : 'untitled_migration';
					$migration->generateSiteMigration($title);
					echo "Complete\n";
				case 'generateModels':
					echo "Generating Models\n";
					$migration->generateModels();
					echo "Complete\n";
					return true;
			}
		}

		return false;
	}

	/**
	 * Sends unrecognised get calls to the controller
	 * @param string $field The field to get
	 */
	public function __get($field) {
		$controller = $this->reg->get("runtime.controller");
		$method = 'get'.ucfirst($field);
		return $controller->$method();
	}

	/**
	 * Sends unrecognised set calls to the controller
	 * @param string $field The field to set
	 * @param mixed $value The value
	 */
	public function __set($field, $value) {
		$controller = $this->reg->get("runtime.controller");
		$method = 'set'.ucfirst($field);
		$controller->$method($value);
	}

	/**
	 * Catches get and set calls **Shouldn't be necessary - above methods should work as method missings on their own
	 * @param string $function The function name
	 * @param array $args The arguments
	 */
	public function __call($function, $args) {
		$opt = substr($function, 0, 3);
		$controller = $this->reg->get("runtime.controller");

		if($opt == 'set')
			return $controller->$function($args[0]);
		else
			return $controller->$function();
	}
}
